package vn.axonactive.devday.jsf.model.stockfeed;

import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.primefaces.model.chart.CartesianChartModel;
import org.primefaces.model.chart.ChartSeries;

import vn.axonactive.devday.jsf.model.StockQuote;


/**
 * The factory class which can be used to:
 * <br>
 * <br> 1. Create list of fake {@link StockQuote} initially. The data of fake {@link StockQuote} is stored in /stock-quotes/src/vn/axonactive/devday/jsf/model/stockfeed/fake_stock.xls file. The client
 * can get list of fake {@link StockQuote} simply by calling {@link StockQuoteFactory#getInstance()#feedData()}.
 * <br>
 * <br> 2. Simulate the fluctuation of given {@link StockQuote} list by calling {@link StockQuoteFactory#getInstance()#simulateFluctuation(List)}. 
 */
public class StockQuoteFactory {
	
	public static final int MAX_PRICE_CHANGE_RATE_ALLOWED = 5;
	
	private static final int MAX_VOLUME_CHANGE_RATE_ALLOWED = 10000;
	private static final int RATIO_FOR_VOLUME = 10000;
	private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#");
	
	private static final StockQuoteFactory instance = new StockQuoteFactory();
	
	private static CartesianChartModel categoryModel = new CartesianChartModel();
	
	private StockQuoteFactory()
	{
		
	}
	
	public static StockQuoteFactory getInstance()
	{
		return instance;
	}
	
	
	/**
	 * Provide a list of fake {@link StockQuote} by reading data from /stock-quotes/src/vn/axonactive/devday/jsf/model/stockfeed/fake_stock.xls file.
	 * 
	 * @return {@link List} of {@link StockQuote}. 
	 * @throws IOException if fail to read fake data from /stock-quotes/src/vn/axonactive/devday/jsf/model/stockfeed/fake_stock.xls file.
	 */
	public List<StockQuote> feedData() throws IOException
	{
		List<StockQuote> stockTickerList = new ArrayList<StockQuote>();
		InputStream inputStream = getClass().getResourceAsStream("fake_stock.xls");
		
		
		HSSFWorkbook workbook = new HSSFWorkbook(inputStream);
		HSSFSheet sheet = workbook.getSheetAt(0);
		
		Iterator<Row> rows = sheet.iterator();
		//skip the header row
		rows.next();
		
		while(rows.hasNext())
		{
			Row row = rows.next();
			//get data from cells
			String symbol = row.getCell(0).getStringCellValue();
			String companyName = row.getCell(1).getStringCellValue();
			double previousClose = row.getCell(2).getNumericCellValue();
			double openPrice = row.getCell(3).getNumericCellValue();
			double currentPrice = row.getCell(4).getNumericCellValue();
			double bidPrice = row.getCell(5).getNumericCellValue();
			double askPrice = row.getCell(6).getNumericCellValue();
			int volume = (int)row.getCell(7).getNumericCellValue();
			
			//parse data into ticker object
			StockQuote ticker = new StockQuote();
			ticker.setSymbol(symbol);
			ticker.setCompanyName(companyName);
			ticker.setCurrentPrice(currentPrice);
			ticker.setVolume(volume);
			ticker.setBidPrice(bidPrice);
			ticker.setAskPrice(askPrice);
			ticker.setOpenPrice(openPrice);
			ticker.setPreviousClose(previousClose);
			
			stockTickerList.add(ticker);
		}
		//close the stream
		inputStream.close();
		
		return stockTickerList ;
		
	}
	
	/**
	 * Randomly select some {@link StockQuote} from given list to simulate the fluctuation. In maximum, half of elements in the list will be changed.
	 * <br>
	 * <br>
	 * For each selected {@link StockQuote}, its changing values are also randomly generated with a limited defined rate.   
	 * 
	 * @param stockQuoteList list of {@link StockQuote} which is the object of fluctuation simulation. 
	 */
	public void simulateFluctuation(List<StockQuote> stockQuoteList)
	{
		if(stockQuoteList==null || stockQuoteList.isEmpty())
		{
			throw new IllegalArgumentException("The input list must be not null or empty");
		}
		
		final int numOfRowChanged = (int) (Math.random()*(double)stockQuoteList.size()/2);
		
		List<Integer> indexList = new ArrayList<Integer>(numOfRowChanged);
		int count = 0;
		while(count<=numOfRowChanged)
		{
			final int randomIndex1 = (int) (Math.random()*(double)stockQuoteList.size());
			indexList.add(randomIndex1);
			count++;
		}
		
		for (Integer idx : indexList) {
			simulateFluctuation(stockQuoteList.get(idx));
		}
		
	}
	
	private void simulateFluctuation(StockQuote stockQuote) {
		
		//add history entry
		stockQuote.createHistoryEntry(stockQuote);
		
		double previousClose = stockQuote.getPreviousClose();
		
		//fluctuate the bid price
		stockQuote.setBidPrice(generateRandomePrice(previousClose));
		
		//fluctuate the ask price
		stockQuote.setAskPrice(generateRandomePrice(previousClose));
		
		//fluctuate the current price
		stockQuote.setCurrentPrice(generateRandomePrice(previousClose));
		
		int volume = stockQuote.getVolume();
		stockQuote.setVolume(generateRandomVolume(volume));
	}

	private double generateRandomePrice(double previousClose)
	{
		double randomAllowedChangeRate = (Math.random()*2-1)*MAX_PRICE_CHANGE_RATE_ALLOWED;
		return Double.valueOf(DECIMAL_FORMAT.format(previousClose*randomAllowedChangeRate/100+ previousClose));
	}
	
	private int generateRandomVolume(int currentVolume)
	{
		return new Random().nextInt((currentVolume + MAX_VOLUME_CHANGE_RATE_ALLOWED)-(currentVolume-MAX_VOLUME_CHANGE_RATE_ALLOWED));
	}

	public static CartesianChartModel getCategoryModel(List<StockQuote> stockQuoteList) {
		ChartSeries price = new ChartSeries();
		ChartSeries volume = new ChartSeries();
		price.setLabel("price");
		volume.setLabel("volume (Ratio: "+ RATIO_FOR_VOLUME+")");
		categoryModel.clear();
		for(StockQuote sq:stockQuoteList){
			price.set(sq.getSymbol(), sq.getCurrentPrice());
			volume.set(sq.getSymbol(), (sq.getVolume()/ RATIO_FOR_VOLUME));
		}
		categoryModel.addSeries(price);
		categoryModel.addSeries(volume);
		return categoryModel;
	}
	
	public static int getMaxValueForChart(List<StockQuote> stockQuoteList) {
		double max=Double.MIN_VALUE;
		for(StockQuote sq:stockQuoteList){
			if (sq.getCurrentPrice() > max) max=sq.getCurrentPrice();
			if ((sq.getVolume()/ RATIO_FOR_VOLUME) > max) max=(sq.getVolume()/ RATIO_FOR_VOLUME);
		}
		return (int) (max + 10);
	}
	
	public void simulateFluctuationForStockQuote(StockQuote selectedStockQuote) {
		simulateFluctuation(selectedStockQuote);
	}
}
